#include "callother.h"

static CoString *nullstring;

CoValue *CoString_FromStringAndSize(const char *raw, ssize_t size)
{
	CoString *str;

	assert(size >= 0);

	if (size == 0 && (str = nullstring) != NULL) {
		CO_INCREF(str);
		return (CoValue *)str;
	}

	str = (CoString *)CO_MALLOC(sizeof(CoString) + size);
	if (str == NULL) {
        /* TODO: [ERROR] no memory */
        return NULL;
    }

    COVALUE_INIT(str, &CoString_Type);
    str->s_size = size;

	str->s_hash = -1;
	if (raw != NULL) {
		memcpy(str->s_val, raw, size);
    }
	str->s_val[size] = '\0';

	if (size == 0) {
		nullstring = str;
		CO_INCREF(str);
    }

	return (CoValue *) str;
}

CoValue *CoString_FromString(const char *raw)
{
    size_t size;
    CoString *str;

    assert(raw != NULL);
    size = strlen(raw);
    if (size > SSIZE_T_MAX) {
        /* TODO: [ERROR] string too long */
        return NULL;
    }

    if (size == 0 && (str = nullstring) != NULL) {
        CO_INCREF(str);
        return (CoValue *)str;
    }

    str = (CoString *)CO_MALLOC(sizeof(CoString) + size);
    if (str == NULL) {
        /* TODO: [ERROR] no memory */
        return NULL;
    }
    COVALUE_INIT(str, &CoString_Type);
    str->s_size = size;

    str->s_hash = -1;
    memcpy(str->s_val, raw, size+1);

    if (size == 0) {
        nullstring = str;
        CO_INCREF(str);
    }

    return (CoValue *)str;
}

CoValue *CoString_Concat(CoValue *va, CoValue *vb)
{
    ssize_t size;
    CoString *str;
    CoString *a;
    CoString *b;

    if (va == NULL || !COSTRING_CHECK(va) || vb == NULL || !COSTRING_CHECK(vb)) {
        /* TODO: cannot concatenate non-strings */
        return NULL;
    }
    a = (CoString *)va;
    b = (CoString *)vb;

	/* Optimize cases with empty left or right operand */
	if ((a->s_size == 0 || b->s_size == 0)) {
		if (a->s_size == 0) {
			CO_INCREF(vb);
			return vb;
		}
		CO_INCREF(va);
		return va;
	}
	size = a->s_size + b->s_size;
	if (size < 0) {
        /* TODO: strings are too large to concatenate */
		return NULL;
	}

	str = (CoString *)CO_MALLOC(sizeof(CoString) + size);
	if (str == NULL) {
        /* TODO: no memory */
		return NULL;
    }
    COVALUE_INIT(str, &CoString_Type);
    str->s_size = size;
	str->s_hash = -1;
	memcpy(str->s_val, a->s_val, a->s_size);
	memcpy(str->s_val + a->s_size, b->s_val, b->s_size);
	str->s_val[size] = '\0';
	return (CoValue *)str;
}

static void string_dealloc(CoValue *self)
{
    assert(COSTRING_CHECK(self));
    CoValue_Free(self);
}

static long string_hash(CoValue *self)
{
    CoString *str;
	ssize_t len;
	unsigned char *p;
	long x;

    assert(COSTRING_CHECK(self));
    str = (CoString *)self;

	if (str->s_hash != -1) {
		return str->s_hash;
    }

	len = str->s_size;
	p = (unsigned char *) str->s_val;
	x = *p << 7;
	while (--len >= 0) {
		x = (1000003*x) ^ *p++;
    }
	x ^= str->s_size;
	if (x == -1)
		x = -2;

	str->s_hash = x;
	return x;
}

static int string_equal(CoValue *va, CoValue *vb)
{
    CoString *sa;
    CoString *sb;

    assert(COSTRING_CHECK(va));
    assert(COSTRING_CHECK(vb));
    sa = (CoString *)va;
    sb = (CoString *)vb;

    return sa->s_size == sb->s_size
        && *sa->s_val == *sb->s_val
        && memcmp(sa->s_val, sb->s_val, sa->s_size) == 0;
}

CoType CoString_Type = {
    "string",
	string_dealloc,                 /* tp_dealloc */
    string_hash,                    /* tp_hash */
    string_equal,                   /* tp_equal */
    0,                              /* tp_str */
    0,                              /* tp_iter */
    0,                              /* tp_iternext */
};

